package com.cwt.framework.config;

import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import com.cwt.common.constant.SysConstant;
import com.cwt.common.enums.RoleEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.ArrayList;
import java.util.List;

/**
 * @author chenwt
 * @version V1.0
 * @describe 【权限校验处理】
 * @date 2023年07月17日 15:14:20
 */
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {

    private static final Logger logger = LoggerFactory.getLogger(SaTokenConfigure.class);

    /**
     * @SaCheckLogin: 登录校验 —— 只有登录之后才能进入该方法。
     * @SaCheckRole("admin"): 角色校验 —— 必须具有指定角色标识才能进入该方法。
     * @SaCheckPermission("user:add"): 权限校验 —— 必须具有指定权限才能进入该方法。
     * @SaCheckSafe: 二级认证校验 —— 必须二级认证之后才能进入该方法。
     * @SaCheckBasic: HttpBasic校验 —— 只有通过 Basic 认证后才能进入该方法。
     * @SaIgnore：忽略校验 —— 表示被修饰的方法或类无需进行注解鉴权和路由拦截器鉴权。
     * @SaCheckDisable("comment")：账号服务封禁校验 —— 校验当前账号指定服务是否被封禁。
     */

    // 注册 Sa-Token 拦截器，打开注解式鉴权功能
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册 Sa-Token 拦截器，校验规则为 StpUtil.checkLogin() 登录校验。
        // 以下代码，我们注册了一个基于 StpUtil.checkLogin() 的登录校验拦截器，
        // 并且排除了/user/doLogin接口用来开放登录（除了/user/doLogin以外的所有接口都需要登录才能访问）

        // 注册登录、注册验证拦截器
        registry.addInterceptor(new SaInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns(notLogin());

        // 开启路由鉴权
        registry.addInterceptor(new SaInterceptor(handler -> {

            // 产品权限、订单管理、财务管理、资料管理、数据中心 权限
            SaRouter.match(authPermission())
                    .notMatch(notAuth())
                    .check(() -> StpUtil.checkRole(RoleEnum.AUTH.getCode()));

            SaRouter.match("/api/menu/**").check(() -> StpUtil.checkPermission("system")); // 权限管理
            SaRouter.match("/api/role/**").check(() -> StpUtil.checkPermission("system")); // 角色管理
            SaRouter.match("/api/user/**").check(() -> StpUtil.checkPermission("system")); // 用户管理

            SaRouter.match("/api/book/**").check(() -> StpUtil.checkPermission("goods")); // 商品管理
            SaRouter.match("/api/collect/**").check(() -> StpUtil.checkPermission("goods")); // 收藏管理

            SaRouter.match("/api/shopCar/**").check(() -> StpUtil.checkPermission("order")); // 购物车管理
            SaRouter.match("/api/order/**").check(() -> StpUtil.checkPermission("order")); // 订单管理
        }))
                .addPathPatterns("/**");
    }

    /**
     * 无需校验的权限集合
     *
     * @return 无需校验的权限集合
     */
    public static List<String> notLogin() {
        List<String> allows = new ArrayList<>();
        allows.add("/api/login");
        allows.add("/api/register");
        allows.add("/api/book/**");
        allows.add("/api/manage/**");

        allows.add("/api/notice/upload");

        // swagger资源放行
        allows.add("/v2/**");
        allows.add("/v3/**");
        allows.add("/swagger-ui.html/**");
        allows.add("/swagger-ui/**");
        allows.add("/doc.html/**");
        allows.add("/api-docs-ext/**");
        allows.add("/swagger-resources/**");
        allows.add("/webjars/**");
        allows.add("/favicon.ico");
        allows.add("/error");
        return allows;
    }

    /**
     * 订单管理、财务管理、资料管理、数据中心 权限
     *
     * @return 订单管理、财务管理、资料管理、数据中心 权限
     */
    public static List<String> authPermission() {
        List<String> allows = new ArrayList<>();

        //产品权限
        allows.add("/ll/channel/product/**");
        allows.add("/api/book/**");
        return allows;
    }

    /**
     * 不鉴权 但是需要登录认证
     *
     * @return 不鉴权 但是需要登录认证
     */
    public static List<String> notAuth() {
        List<String> allows = new ArrayList<>();
        allows.add("/ll/channel/product/list/que");
        return allows;
    }

    public void addInterceptors(InterceptorRegistry registry, String local) {
        // 2、校验函数详解

        // SaRouter.match() 匹配函数有两个参数：
        //          参数一：要匹配的path路由。
        //          参数二：要执行的校验函数。
        registry.addInterceptor(new SaInterceptor(handler -> {
                    // 指定一条 match 规则
                    SaRouter
                            .match("/**") // 拦截的 path 列表，可以写多个 */
                            .notMatch("/login") // 排除掉的 path 列表，可以写多个
                            .check(r -> StpUtil.checkLogin()); // 要执行的校验动作，可以写完整的 lambda 表达式

                    // 角色校验 -- 拦截以 admin 开头的路由，必须具备 admin 角色或者 super-admin 角色才可以通过认证
                    SaRouter.match("/admin/**", r -> StpUtil.checkRoleOr("admin", "super-admin"));

                    // 根据路由划分模块，不同模块不同鉴权
                    SaRouter.match("/user/**", r -> StpUtil.checkPermission("user"));
                    SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
                    SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
                    SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
                    SaRouter.match("/notice/**", r -> StpUtil.checkPermission("notice"));
                    SaRouter.match("/comment/**", r -> StpUtil.checkPermission("comment"));

                    // 甚至你可以随意的写一个打印语句
                    SaRouter.match("/**", r -> logger.info("----啦啦啦----"));

                    // 连缀写法
                    SaRouter.match("/**").check(r -> logger.info("----啦啦啦----"));

                    // 3、匹配特征详解
                    // 基础写法样例：匹配一个path，执行一个校验函数
                    SaRouter.match("/user/**").check(r -> StpUtil.checkLogin());

                    // 根据 path 路由匹配   ——— 支持写多个path，支持写 restful 风格路由
                    // 功能说明: 使用 /user , /goods 或者 /art/get 开头的任意路由都将进入 check 方法
                    SaRouter.match("/user/**", "/goods/**", "/art/get/{id}").check( /* 要执行的校验函数 */ r -> logger.info("----啦啦啦----"));

                    // 根据 path 路由排除匹配
                    // 功能说明: 使用 .html , .css 或者 .js 结尾的任意路由都将跳过, 不会进入 check 方法
                    SaRouter.match("/**").notMatch("*.html", "*.css", "*.js").check( /* 要执行的校验函数 */ r -> logger.info("----啦啦啦----"));

                    // 根据请求类型匹配
                    SaRouter.match(SaHttpMethod.GET).check( /* 要执行的校验函数 */ r -> logger.info("----啦啦啦----"));

                    // 根据一个 boolean 条件进行匹配
                    SaRouter.match(StpUtil.isLogin()).check( /* 要执行的校验函数 */ r -> logger.info("----啦啦啦----"));

                    // 根据一个返回 boolean 结果的lambda表达式匹配
                    SaRouter.match(r -> StpUtil.isLogin()).check( /* 要执行的校验函数 */ r -> logger.info("----啦啦啦----"));

                    // 多个条件一起使用
                    // 功能说明: 必须是 Get 请求 并且 请求路径以 `/user/` 开头
                    SaRouter.match(SaHttpMethod.GET).match("/user/**").check( /* 要执行的校验函数 */ r -> logger.info("----啦啦啦----"));

                    // 可以无限连缀下去
                    // 功能说明: 同时满足 Get 方式请求, 且路由以 /admin 开头, 路由中间带有 /send/ 字符串, 路由结尾不能是 .js 和 .css
                    SaRouter
                            .match(SaHttpMethod.GET)
                            .match("/admin/**")
                            .match("/**/send/**")
                            .notMatch("/**/*.js")
                            .notMatch("/**/*.css")
                            // ....
                            .check( /* 只有上述所有条件都匹配成功，才会执行最后的check校验函数 */ r -> logger.info("----啦啦啦----"));

                    // 4、提前退出匹配链
                    // 执行back函数后将停止匹配，也不会进入Controller，而是直接将 back参数 作为返回值输出到前端
                    SaRouter.match("/user/back").back("要返回到前端的内容");

                    // 如下示例，代码运行至第2条匹配链时，会在stop函数处提前退出整个匹配函数，从而忽略掉剩余的所有match匹配
                    // SaRouter.match("/**").check(r -> System.out.println("进入1"));
                    // SaRouter.match("/**").check(r -> System.out.println("进入2")).stop();
                    // SaRouter.match("/**").check(r -> System.out.println("进入3"));

                    // stop() 与 back() 函数的区别在于：
                    //      SaRouter.stop() 会停止匹配，进入Controller。
                    //      SaRouter.back() 会停止匹配，直接返回结果到前端。


                    // 5、使用free打开一个独立的作用域
                    // 进入 free 独立作用域
                    SaRouter.match("/**").free(r -> {
                        SaRouter.match("/a/**").check(/* --- */ a -> logger.info("----啦啦啦----"));
                        SaRouter.match("/b/**").check(/* --- */ a -> logger.info("----啦啦啦----")).stop();
                        SaRouter.match("/c/**").check(/* --- */ a -> logger.info("----啦啦啦----"));
                    });

                    // 执行 stop() 函数跳出 free 后继续执行下面的 match 匹配
                    // SaRouter.match("/**").check(/* --- */ r -> logger.info("----啦啦啦----"));

                    // 6、使用注解忽略掉路由拦截校验 >> 我们可以使用 @SaIgnore 注解，忽略掉路由拦截认证：
                    //      注意点：此注解的忽略效果只针对 SaInterceptor拦截器 和 AOP注解鉴权 生效，对自定义拦截器与过滤器不生效。
                })
                        // 7、关闭注解校验
                        .isAnnotation(false) // 指定关闭掉注解鉴权能力，这样框架就只会做路由拦截校验了

        ).addPathPatterns("/**");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 商品图片等定义静态资源映射
        registry.addResourceHandler("/images/**").addResourceLocations("file:" + SysConstant.ORDER_UPLOAD_DIR);

        // 系统公告文件图片地址映射
        registry.addResourceHandler("/noticeImg/**").addResourceLocations("file:" + SysConstant.SYS_NOTICE_DIR);
    }
}
